#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "log.h"

#include "thread.h"

#ifndef NO_THREAD

void cs_init(Mutex *cs, const char name[32])
{
    s32 ret = pthread_mutex_init(&cs->mutex, NULL);
    cs->name[0] = '\0';
    strncpy(cs->name, name, 32);
    cs->name[31] = '\0';
    if (ret)
        _log(ERR, CORE, "Failed to init mutex '%s': %s!", name, strerror(ret));
}

void cs_free(Mutex *cs)
{
    s32 ret = pthread_mutex_destroy(&cs->mutex);
    if (ret)
        _log(ERR, CORE, "Failed to free mutex '%s': %s!", cs->name, strerror(ret));
}

void cs_enter(Mutex *cs)
{
    s32 ret = pthread_mutex_lock(&cs->mutex);
    if (ret)
        _log(ERR, CORE, "Failed to lock mutex '%s': %s!", cs->name, strerror(ret));
}

void cs_leave(Mutex *cs)
{
    s32 ret = pthread_mutex_unlock(&cs->mutex);
    if (ret)
        _log(ERR, CORE, "Failed to unlock mutex '%s': %s!", cs->name, strerror(ret));
}

void thread_init(Thread *thr, void *(*function) (void*), void* arg)
{
    s32 ret = pthread_create(&thr->thread_id, NULL, function, arg);
    if (ret)
        _log(ERR, CORE, "Failed to create thread (id %p, function %p, arg %p): %s", &thr->thread_id, function, arg, strerror(ret));
    else
        _log(INF, CORE, "Created new thread %lu (func = %p, arg = %p)", thr->thread_id, function, arg);
}

void thread_waitForDeath(Thread *thr)
{
    s32 ret = pthread_join(thr->thread_id, NULL);
    if (ret)
        _log(ERR, CORE, "Failed to wait for thread %lu death: %s", thr->thread_id, strerror(ret));
    else
        _log(INF, CORE, "Thread %lu exited", thr->thread_id);
}

u8 thread_isActual(Thread *thr)
{
    return (pthread_self() == thr->thread_id);
}

void thread_stop(Thread *thr)
{
    pthread_cancel(thr->thread_id);
}

Condition *thread_cond_init(u32 wait_ms)
{
    Condition *cond = malloc(sizeof(Condition));
    s32 ret = pthread_cond_init(&cond->cond, NULL);
    if (ret)
        _log(ERR, CORE, "Failed to initialize condition: %s", strerror(ret));
    cond->sec = wait_ms / 1000;
    cond->nsec = (wait_ms % 1000) * 1000 * 1000;
    return cond;
}

s8 thread_cond_timedWait(Condition *cond, Mutex *mtx)
{
    s32 ret;
    struct timespec ts;

#ifdef UNIX
    struct timeval  tp;

    if (gettimeofday(&tp, NULL) != 0)
        _log(ERR, CORE, "gettimeofday() failed!");

    ts.tv_sec  = tp.tv_sec;
    ts.tv_nsec = tp.tv_usec * 1000;
#else
    SYSTEMTIME st;
    GetSystemTime(&st);

    ts.tv_sec  = st.wSecond + st.wMinute * 60 + st.wHour * 3600;
    ts.tv_nsec = st.wMilliseconds * 1000 * 1000;
#endif

    ts.tv_sec  += cond->sec;
    ts.tv_nsec += cond->nsec;
    ts.tv_sec  += ((u64)ts.tv_nsec) / (1000 * 1000 * 1000);
    ts.tv_nsec  = ((u64)ts.tv_nsec) % (1000 * 1000 * 1000);

    ret = pthread_cond_timedwait(&cond->cond, &mtx->mutex, &ts);
    if (ret == ETIMEDOUT)
        return COND_TIMEOUT; /* not received */
    else if (ret != 0) {
        _log(ERR, CORE, "Failed to wait timed condition: %s", strerror(ret));
        return COND_TIMEOUT;
    }

    return COND_MET;
}

void thread_cond_wait(Condition *cond, Mutex *mtx)
{
    if (pthread_cond_wait(&cond->cond, &mtx->mutex) != 0)
        _log(ERR, CORE, "Failed to wait condition");
}

void thread_cond_signal(Condition *cond)
{
    if (pthread_cond_signal(&cond->cond) != 0)
        _log(ERR, CORE, "Failed to signal condition");
}

void thread_cond_free(Condition *cond)
{
    pthread_cond_destroy(&cond->cond);
    free(cond);
}

#endif /* NO_THREAD */

